#pragma once

#include "Socket.hpp"
#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <memory>

using namespace NetWork;

const static int num = sizeof(fd_set) * 8;

class SelectServer
{
public:
    SelectServer(int port) : _port(port),
                             _listen_socket(new TcpSocket(port)), _is_running(false)
    {
    }

    void init()
    {
        _listen_socket->BuildListenSocketMethod(_port, default_backlog);

        for (int i = 0; i < num; i++)
        {
            _rfds_array[i] = nullptr;
        }
        _rfds_array[0] = _listen_socket;
    }

    void loop()
    {
        _is_running = true;
        while (_is_running)
        {
            // use select to wait for socket events
            fd_set read_fds;
            FD_ZERO(&read_fds);
            
            int max_fd = _listen_socket->GetSockfd();
            for (int i = 0; i < num; i++)
            {
                if (_rfds_array[i] == nullptr) { continue; }

                int sockfd = _rfds_array[i]->GetSockfd();
                if (sockfd > max_fd)
                {
                    max_fd = sockfd;
                }
                FD_SET(sockfd, &read_fds);  // add all sockfd to read_fds
            }

            struct timeval timeout
            {
                3, 0
            }; // 3 seconds
            // timeout is not 0, so select will block until timeout
            // timeout is 0, so select will not block catching events
            // timeout is nullptr, select will block indefinitely

            // select() will modify the fds, before call select, we can print triggered fd debug info
            PrintDebug();

            int ret = select(max_fd + 1, &read_fds, nullptr, nullptr, /*&timeout*/ nullptr);

            if (ret == -1)
            {
                std::cout << "call select error" << std::endl;
            }
            else if (ret == 0)
            {
                // timeout, do something
                std::cout << "select timeout... [timeout = 3 seconds]" << std::endl;
            }
            else
            {
                // handle socket events and call accept function
                HandlerEvent(read_fds);
                // std::cout << "select success! handle socket events  [remaining time = "
                //     << timeout.tv_sec << "." << timeout.tv_usec << " seconds]" << std::endl;
            }
        }
        _is_running = false;
    }

private:
    void HandlerEvent(fd_set &read_fds)
    {
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
            {
                continue;
            }

            // valid sockfd
            int sockfd = _rfds_array[i]->GetSockfd();
            if (FD_ISSET(sockfd, &read_fds))
            {
                // 1. read event is triggered (new link is available)
                if (sockfd == _listen_socket->GetSockfd())
                {
                    // new connection
                    std::cout << "new connection" << std::endl;
                    // client socket will not block, because listen_socket is ready to accept
                    std::string client_ip;
                    uint16_t client_port;
                    std::shared_ptr<Socket> client_socket = _listen_socket->AcceptSocketOrDie(&client_ip, &client_port);
                    if (client_socket)
                    {
                        printf("new connection from %s:%d\n", client_ip.c_str(), client_port);
                    }
                    else
                    {
                        std::cout << "accept socket error!!!" << std::endl;
                        continue;
                    }

                    // add client socket to _rfds_array
                    int pos = 0;
                    for (; pos < num; pos++)
                    {
                        if (_rfds_array[pos] == nullptr)
                        {
                            _rfds_array[pos] = client_socket;
                            std::cout << "get a new link, fd = " << client_socket->GetSockfd() << std::endl;
                            break;
                        }
                    }
                    if (pos == num)
                    {
                        std::cout << "ERROR: too many clients, please increase the size of _rfds_array" << std::endl;
                        // TODO: close client_socket
                        client_socket->CloseSocket();
                    }
                }
            }
            else
            {
                // 2. data is available for reading
                std::string data;
                bool ret = _rfds_array[i]->Recv(&data, 1024);
                if (ret)
                {
                    // recv available data from client
                    std::cout << "recv data from client # " << data << std::endl;
                    _rfds_array[i]->Send("echo received data: [" + data + "]");
                }
                else
                {
                    // client closed connection
                    std::cout << "client closed connection" << std::endl;
                    _rfds_array[i]->CloseSocket();
                    _rfds_array[i] = nullptr;
                }
            }
        }
    }

    void PrintDebug()
    {
        std::cout << "current select rfds list is : ";
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            else
                std::cout << _rfds_array[i]->GetSockfd() << " ";
        }
        std::cout << std::endl;
        sleep(1);
    }

private:
    std::shared_ptr<Socket> _listen_socket;
    int _port;
    bool _is_running;
    std::shared_ptr<Socket> _rfds_array[num];
};